package com.kaguya.prototype;

import java.io.*;

/**
 * 设计模式 创建型：原型模式，对象通过clone来快速创建
 */
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        //原型
        BaseInfo baseInfo = new BaseInfo("608阿巴阿巴阿巴");
        Product product = new Product("1", "2", "3", "4",baseInfo);
        //调用原型的clone方法
        Product clone1 = (Product)product.clone();

        //无论是更改引用本身，还是原型的引用，还是克隆对象的引用，都会进行更改
        //输出对比发现，原型对象中的引用类型也被克隆，hashCode一致，说明是同一对象

        //更改原型的引用
        baseInfo.setCompanyName("608喝了咯");
        product.getBaseInfo().setCompanyName("608秒内噶");
        clone1.getBaseInfo().setCompanyName("608喵内");


        //打印
        System.out.println("prototype:"+product.toString());
        System.out.println("protoBasInfo:"+product.getBaseInfo().toString());
        System.out.println("---------------------------------------------------");
        System.out.println("clonetype::"+clone1.toString());
        System.out.println("cloneBaseInfo:"+clone1.getBaseInfo().toString());

        /**
         * 深拷贝和浅拷贝：基础类型存在堆中，引用类型的值存在栈中，堆中存的是对这个值的引用
         * 浅拷贝，对于基本类型 仅仅拷贝了值，没有拷贝引用，（基础类型都是浅拷贝，ArrayList中虽然实现了cloneable接口但仍然是浅拷贝）
         * 浅拷贝 新建引用，引用指向同一个值或者说地址
         * 深拷贝指新建引用，引用指向新的值或者说地址
         */
    }
}

/**
 *模拟复杂类型对象
 */
class BaseInfo implements Serializable {
    private String companyName;

    @Override
    public String toString() {
        return super.hashCode()+" | BaseInfo{" +
                "companyName='" + companyName + '\'' +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public BaseInfo() {

    }

    public BaseInfo(String companyName) {
        this.companyName = companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    public String getCompanyName() {
        return companyName;
    }
}

/**
 * 复杂对象模拟
 */
class Product implements Cloneable,Serializable{
    private String part1;
    private String part2;
    private String part3;
    private String part4;
    //复杂引用类型
    private BaseInfo baseInfo;
    //......

    //重写Objec类的clone方法，进行对象的拷贝

    /**
     * 实现了cloneable接口后，标记该类可进行clone，
     * 如果没有重写Object对象的clone方法会抛出CloneNotSupportedException异常
     * 通过该对象的clone方法可进行对象的拷贝，以快速基于原型创建多个对象
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        //v1
//        //若要实现深拷贝，不仅需要拷贝自身，还要调用自己的复杂类型属性的clone方法
//        this.setBaseInfo((BaseInfo) this.getBaseInfo().clone());
//        return super.clone();
        //v2
        //通过序列化和反序列化实现深拷贝

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        try (ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);) {
            //将对象输出到磁盘中
            oos.writeObject(this);
        } catch (IOException e) {
            e.printStackTrace();
        }


        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        try(ObjectInputStream ois = new ObjectInputStream(byteArrayInputStream);){
            //从磁盘中读取对象
            Product product = (Product) ois.readObject();
            return product;
        }catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    public Product() {
    }

    public Product(String part1, String part2, String part3, String part4,BaseInfo baseInfo) {
        this.part1 = part1;
        this.part2 = part2;
        this.part3 = part3;
        this.part4 = part4;
        this.baseInfo = baseInfo;
    }

    @Override
    public String toString() {
        return super.hashCode()+" | Product{" +
                "part1='" + part1 + '\'' +
                ", part2='" + part2 + '\'' +
                ", part3='" + part3 + '\'' +
                ", part4='" + part4 + '\'' +
                ", baseInfo=" + baseInfo +
                '}';
    }

    public void setPart1(String part1) {
        this.part1 = part1;
    }

    public void setPart2(String part2) {
        this.part2 = part2;
    }

    public void setPart3(String part3) {
        this.part3 = part3;
    }

    public void setPart4(String part4) {
        this.part4 = part4;
    }

    public void setBaseInfo(BaseInfo baseInfo) {
        this.baseInfo = baseInfo;
    }

    public String getPart1() {
        return part1;
    }

    public String getPart2() {
        return part2;
    }

    public String getPart3() {
        return part3;
    }

    public String getPart4() {
        return part4;
    }

    public BaseInfo getBaseInfo() {
        return baseInfo;
    }
}
